//***************************************************************************************
// Simple first person style camera class that lets the viewer explore the 3D scene.
//   -It keeps track of the camera coordinate system relative to the world space
//    so that the view matrix can be constructed.  
//   -It keeps track of the viewing frustum of the camera so that the projection
//    matrix can be obtained.
//***************************************************************************************

#pragma once

#include <DirectXMath.h>

namespace D3D10Utils
{
	class Camera
	{
	public:
		Camera();
		~Camera();
	
		// Get/Set world camera position.
		__forceinline const DirectX::XMFLOAT3A& getPosition() const;
		__forceinline void setPosition(const float x, const float y, const float z);
		__forceinline void setPosition(const DirectX::XMFLOAT3A& v);
		
		// Get camera basis vectors.
		__forceinline const DirectX::XMFLOAT3A& getRight() const;
		__forceinline const DirectX::XMFLOAT3A& getUp() const;
		__forceinline const DirectX::XMFLOAT3A& getLook() const;
	
		// Get frustum properties.
		__forceinline float getNearZ() const;
		__forceinline float getFarZ() const;
		__forceinline float getAspect() const;
		__forceinline float getFovY() const;
		__forceinline float getFovX() const;
	
		// Get near and far plane dimensions in view space coordinates.
		__forceinline float getNearWindowWidth() const;
		__forceinline float getNearWindowHeight() const;
		__forceinline float getFarWindowWidth() const;
		__forceinline float getFarWindowHeight() const;
		
		// Set frustum.
		void setLens(const float fovY, const float aspect, const float nearPlaneZ, const float farPlaneZ);
	
		// Define camera space via LookAt parameters.
		void lookAt(DirectX::FXMVECTOR pos, DirectX::FXMVECTOR target, DirectX::FXMVECTOR worldUp);
		void lookAt(const DirectX::XMFLOAT3A& pos, const DirectX::XMFLOAT3A& target, const DirectX::XMFLOAT3A& up);
	
		// Get View/Proj matrices.
		__forceinline const DirectX::XMFLOAT4X4A& getViewMatrix() const;
		__forceinline const DirectX::XMFLOAT4X4A& getProjectionMatrix() const;
	
		// Strafe/Walk the camera a distance d.
		void strafe(const float d);
		void walk(const float d);
	
		// Rotate the camera.
		void pitch(const float angle);
		void yaw(const float angle);
	
		// After modifying camera position/orientation, call to rebuild the view matrix.
		void updateViewMatrix();
	
	private:
		// Camera coordinate system with coordinates relative to world space.
		DirectX::XMFLOAT3A mPosition;
		DirectX::XMFLOAT3A mRight;
		DirectX::XMFLOAT3A mUp;
		DirectX::XMFLOAT3A mLook;
	
		// Cache frustum properties.
		float mNearZ;
		float mFarZ;
		float mAspect;
		float mFovY;
		float mNearWindowHeight;
		float mFarWindowHeight;
	
		// Cache View/Proj matrices.
		DirectX::XMFLOAT4X4A mView;
		DirectX::XMFLOAT4X4A mProjection;
	};
	
	const DirectX::XMFLOAT3A& Camera::getPosition() const
	{
		return mPosition;
	}
	
	void Camera::setPosition(const float x, const float y, const float z)
	{
		mPosition = DirectX::XMFLOAT3A(x, y, z);
	}
	
	void Camera::setPosition(const DirectX::XMFLOAT3A& v)
	{
		mPosition = v;
	}
	
	const DirectX::XMFLOAT3A& Camera::getRight() const
	{
		return mRight;
	}
		
	const DirectX::XMFLOAT3A& Camera::getUp() const
	{
		return mUp;
	}
	
	const DirectX::XMFLOAT3A& Camera::getLook() const
	{
		return mLook;
	}
	
	float Camera::getNearZ() const
	{
		return mNearZ;
	}
	
	float Camera::getFarZ() const
	{
		return mFarZ;
	}
	
	float Camera::getAspect() const
	{
		return mAspect;
	}
	
	float Camera::getFovY() const
	{
		return mFovY;
	}
	
	float Camera::getFovX() const
	{
		const float halfWidth = 0.5f * getNearWindowWidth();
		
		return 2.0f * atan(halfWidth / mNearZ);
	}
	
	float Camera::getNearWindowWidth() const
	{
		return mAspect * mNearWindowHeight;
	}
	
	float Camera::getNearWindowHeight() const
	{
		return mNearWindowHeight;
	}
	
	float Camera::getFarWindowWidth() const
	{
		return mAspect * mFarWindowHeight;
	}
	
	float Camera::getFarWindowHeight() const
	{
		return mFarWindowHeight;
	}

	const DirectX::XMFLOAT4X4A& Camera::getViewMatrix() const
	{
		return mView;
	}
	
	const DirectX::XMFLOAT4X4A& Camera::getProjectionMatrix() const
	{
		return mProjection;
	}

    Camera& GetCamera();
}
